#include <boost/python.hpp>
#include <boost/python/stl_iterator.hpp>
#include <boost/python/suite/indexing/vector_indexing_suite.hpp>
#include <boost/python/exception_translator.hpp>
#include <boost/python/detail/wrap_python.hpp>
#include <numpy/arrayobject.h>
#include "Device.h"
#include "Pendant.h"
#include "SignalProcessing.h"

using namespace device;
using namespace boost::python;

// general purpose stuff
template<typename Exception>
void translate_exception(Exception const& e) {
    PyErr_SetString(PyExc_RuntimeError, e.what());
}

#define WRAP_VECTOR(type, noproxy, name) \
class_< vector< type > >("Vector_" #name) \
    .def(vector_indexing_suite< vector< type >, noproxy >())

// signals, properties, signalingproperties
struct GilFunctionVoid {
    object function;
    GilFunctionVoid(object function_) : function(function_) {}
    void operator()() {
        PyGILState_STATE gstate = PyGILState_Ensure();
        function();
        PyGILState_Release(gstate);
    }
};
template<typename T>
struct GilFunction {
    object function;
    GilFunction(object function_) : function(function_) {}
    void operator()(T val) {
        PyGILState_STATE gstate = PyGILState_Ensure();
        function(val);
        PyGILState_Release(gstate);
    }
};
template<typename T>
shared_ptr<boost::signals2::scoped_connection> 
signal_connect(boost::signals2::signal<void(T)>& sig, object slot) {
    return shared_ptr<boost::signals2::scoped_connection>
        (new boost::signals2::scoped_connection(sig.connect(GilFunction<T>(slot))));
}

#define WRAP_SIGNAL_WITH_CONNECT(type,name,connect) \
class_< boost::signals2::signal<void (type)>, bases<>, \
    shared_ptr< boost::signals2::signal<void (type)> >, boost::noncopyable >("Signal" #name, no_init) \
    .def("connect", &connect, return_value_policy<return_by_value>());

#define WRAP_SIGNAL(type,name) \
    WRAP_SIGNAL_WITH_CONNECT(type,name,signal_connect<type>)

#define WRAP_PROPERTY(type,name) \
class_< Property<type> >("Property_" #name, no_init) \
    .add_property("val", make_function(&Property<type>::operator(), \
        return_value_policy<return_by_value>()))

#define WRAP_SIGNALING_PROPERTY(type,name) \
WRAP_PROPERTY(type,name); \
WRAP_SIGNAL(type,name); \
class_< SignalingProperty<type>, bases< Property<type> >, \
    shared_ptr< SignalingProperty<type> >, boost::noncopyable >("SignalingProperty_" #name, no_init) \
    .def_readonly("signal", &SignalingProperty<type>::signal) 

// io_service

shared_ptr<boost::thread> io_service_run_thread(boost::asio::io_service* io_serviceP) {
    size_t (boost::asio::io_service::*io_service_run)()=&boost::asio::io_service::run;
    return shared_ptr<boost::thread>
        (new boost::thread(io_service_run, io_serviceP));
}
void io_service_post(boost::asio::io_service* io_serviceP, object func) {
    io_serviceP->post(GilFunctionVoid(func));
}

// Recorder
shared_ptr< Recorder<double> > newRecorder(object py_channels, object py_samples) {
    stl_input_iterator<uint8_t> begin(py_channels), end;
    int samples=extract<int>(py_samples);
    return shared_ptr< Recorder<double> >(new Recorder<double>(begin,end,samples));
}
handle<> recorder_data(Recorder<double>& recorder) {
    npy_intp dims[2] = {recorder.iisample, recorder.channels.size()};
    return handle<>(borrowed(PyArray_SimpleNewFromData(2, dims, PyArray_DOUBLE, recorder.data)));
}
handle<> shiftfft_samples(ShiftFFT& fft) {
    npy_intp dims[1] = {fft.size};
    return handle<>(borrowed(PyArray_SimpleNewFromData(1, dims, PyArray_DOUBLE, fft.in)));
}
handle<> shiftfft_amplitude(ShiftFFT& fft) {
    npy_intp dims[1] = {fft.size/2 + 1};
    return handle<>(borrowed(PyArray_SimpleNewFromData(1, dims, PyArray_DOUBLE, fft.amplitude)));
}

BOOST_PYTHON_MODULE(device) {

PyEval_InitThreads();

register_exception_translator<device::exception>
    (&translate_exception<device::exception>);

class_<boost::signals2::connection>("signal_connection", no_init)
    .def("disconnect", &boost::signals2::scoped_connection::disconnect)
    .add_property("connected", &boost::signals2::scoped_connection::connected)
    .add_property("blocked", &boost::signals2::scoped_connection::blocked)
;
class_<boost::signals2::scoped_connection, bases<boost::signals2::connection>,
    shared_ptr<boost::signals2::scoped_connection>, boost::noncopyable>("signal_scoped_connection", no_init)
;

WRAP_SIGNALING_PROPERTY(bool, bool);
WRAP_SIGNALING_PROPERTY(uint16_t, uint16_t);
WRAP_SIGNAL(Sample, Sample);
WRAP_VECTOR( shared_ptr< SignalingProperty<bool> >, true, SignalingPropertyBoolPtr);
WRAP_VECTOR( double, false, double);

class_<boost::thread, bases<>,
    shared_ptr<boost::thread>, boost::noncopyable>("boost_thread", no_init)
    .add_property("joinable", &boost::thread::joinable)
    .def("join", &boost::thread::join)
    .def("detach", &boost::thread::detach)
    .def("interrupt", &boost::thread::interrupt)
;

size_t (boost::asio::io_service::*io_service_run)()=&boost::asio::io_service::run;
size_t (boost::asio::io_service::*io_service_run_one)()=&boost::asio::io_service::run_one;
size_t (boost::asio::io_service::*io_service_poll)()=&boost::asio::io_service::poll;
size_t (boost::asio::io_service::*io_service_poll_one)()=&boost::asio::io_service::poll_one;
class_<boost::asio::io_service, bases<>, 
       shared_ptr<boost::asio::io_service>, boost::noncopyable>("io_service")
    .def("run_thread", io_service_run_thread)
    .def("run", io_service_run)
    .def("run_one", io_service_run_one)
    .def("poll", io_service_poll)
    .def("poll_one", io_service_poll_one)
    .def("stop", &boost::asio::io_service::stop)
    .def("reset", &boost::asio::io_service::reset)
    .def("post", &io_service_post)
;

class_<Sample>("Sample", no_init)
    .def("__len__", &Sample::size, return_value_policy<return_by_value>())
    .def("__getitem__", &Sample::operator[], return_value_policy<return_by_value>())
;    
class_<Device, bases<>, 
       shared_ptr<Device>, boost::noncopyable>("Device", no_init)
    .def_readonly("sample", &Device::sample)
    .def_readonly("sync", &Device::sync)
    .def_readonly("battery", &Device::battery)
    .def_readonly("sampling_rate", &Device::sampling_rate)
    .def_readonly("button", &Device::button)
    .def_readonly("channel_ok", &Device::channel_ok)
;
class_< pendant::Device, bases<Device>, 
        shared_ptr<pendant::Device>, boost::noncopyable>("Pendant",
    init<boost::asio::io_service&, const char*>())
    .def("reset", &pendant::Device::reset)
    .def("read", &pendant::Device::read)
;

import_array(); // initialize numpy to allow call to PyArray_SimpleNewFromData in recorder_data
class_< Recorder<double> >("Recorder", no_init)
    .def("__init__", make_constructor(&newRecorder))
    .def("sampleReceived", &Recorder<double>::sampleReceived)
    .def("syncChanged", &Recorder<double>::syncChanged)
    .add_property("samples", &recorder_data)
;

class_< ShiftFFT >("ShiftFFT",
    init<size_t,size_t>())
    .def("sampleReceived", &ShiftFFT::sampleReceived)
    .def("syncChanged", &ShiftFFT::syncChanged)
    .def_readonly("not_ready", &ShiftFFT::not_ready)
    .add_property("amplitude", &shiftfft_amplitude)
    .add_property("samples", &shiftfft_samples)
;
}
